﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;

namespace Kaesoft.Hospedaje.Entities
{
    [DataContract]
    public abstract class EntityValidator : INotifyDataErrorInfo
    {
        #region Variables
        protected object Entity;
        private IDictionary<string, IList<PropertyValidationRule>> Rules = new Dictionary<string, IList<PropertyValidationRule>>();
        private readonly IDictionary<string, IList<ErrorMessage>> errors = new Dictionary<string, IList<ErrorMessage>>();
        #endregion

        #region Eventos
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="entity">Entidad</param>
        public EntityValidator(IPropertyChangedRaiser entity)
        {
            this.Entity = entity;
        }

        /// <summary>
        /// Existen errores?
        /// </summary>
        public bool HasErrors
        {
            get { return errors.Values.Count > 0; }
        }

        #endregion

        #region Metodos Publicos
        /// <summary>
        /// Añadir regla de validacion
        /// </summary>
        public void AddPropertyRule(string propertyName, ValidationRuleDelegate validationRule, string errorMessage, ErrorType errorType = ErrorType.Required, params object[] args)
        {
            IList<PropertyValidationRule> propertyRules;
            if (!Rules.TryGetValue(propertyName, out propertyRules))
            {
                propertyRules = new List<PropertyValidationRule>();
                Rules.Add(propertyName, propertyRules);
            }
            var rule = new PropertyValidationRule
            {
                PropertyName = propertyName,
                ValidationRule = validationRule,
                ErrorMessage = errorMessage,
                ErrorType = errorType,
                Args = args
            };
            propertyRules.Add(rule);
        }

        /// <summary>
        /// Verificar si tiene metodo y ejecutar la validacion
        /// </summary>
        /// <param name="propertyName">Propiedad</param>
        [DebuggerStepThrough]
        public void Validate(string propertyName)
        {
            IList<PropertyValidationRule> propertyRules;
            if (Rules.TryGetValue(propertyName, out propertyRules))
            {
                foreach (var propertyRule in propertyRules)
                {
                    if (!propertyRule.ValidationRule(Entity, propertyRule.PropertyName, propertyRule.Args))
                    {
                        AddError(propertyRule.PropertyName, propertyRule.ErrorMessage, propertyRule.ErrorType);
                    }
                }
            }
        }

        /// <summary>
        /// Verificar si tiene metodo y ejecutar la validacion
        /// </summary>
        /// <param name="propertyName">Propiedad</param>
        public IList<ErrorMessage> ValidateOne(string propertyName)
        {
            IList<PropertyValidationRule> propertyRules;
            if (Rules.TryGetValue(propertyName, out propertyRules))
            {
                foreach (var propertyRule in propertyRules)
                {
                    if (!propertyRule.ValidationRule(Entity, propertyRule.PropertyName, propertyRule.Args))
                    {
                        AddError(propertyRule.PropertyName, propertyRule.ErrorMessage, propertyRule.ErrorType);
                    }
                }
            }

            return errors.Where(err => err.Key == propertyName).SelectMany(err => err.Value).ToList();
        }

        /// <summary>
        /// Verificar si cada propiedad tiene metodo y ejecutar validacion
        /// </summary>
        public IList<ErrorMessage> ValidateAll()
        {
            errors.Clear();
            foreach (var rule in Rules)
            {
                IList<PropertyValidationRule> propertyRules = rule.Value;
                foreach (var propertyRule in propertyRules)
                {
                    if (!propertyRule.ValidationRule(Entity, propertyRule.PropertyName, propertyRule.Args))
                    {
                        AddError(propertyRule.PropertyName, propertyRule.ErrorMessage, propertyRule.ErrorType);
                    }
                }

            }
            return errors.SelectMany(err => err.Value).ToList();
        }

        /// <summary>
        /// Verificar si cada propiedad tiene metodo y ejecutar validacion
        /// </summary>
        public IList<ErrorMessage> ValidateMany(IList<string> properties)
        {
            errors.Clear();
            IList<PropertyValidationRule> propertyRules;

            foreach (var property in properties)
            {
                if (Rules.TryGetValue(property, out propertyRules))
                {
                    foreach (var propertyRule in propertyRules)
                    {
                        if (!propertyRule.ValidationRule(Entity, propertyRule.PropertyName, propertyRule.Args))
                        {
                            AddError(propertyRule.PropertyName, propertyRule.ErrorMessage, propertyRule.ErrorType);
                        }
                    }
                }
            }
           return errors.Where(ent => properties.Contains(ent.Key)).SelectMany(err => err.Value).ToList();
        }

        /// <summary>
        /// Eliminar los errores
        /// </summary>
        /// <param name="propertyName">Propiedad</param>
        public void ClearErrors(string propertyName)
        {
            IList<ErrorMessage> propertyErrors;
            if (errors.TryGetValue(propertyName, out propertyErrors))
            {
                if (propertyErrors.Count > 0)
                {
                    propertyErrors.Clear();
                    OnErrorsChanged(propertyName);
                }
            }
        }

        /// <summary>
        /// Obtener los errores
        /// </summary>
        /// <param name="propertyName">Propiedad</param>
        /// <returns></returns>
        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            IList<ErrorMessage> propertyErrors;
            errors.TryGetValue(propertyName, out propertyErrors);
            return propertyErrors!=null?propertyErrors.Select(p=> p.ErrorText):new List<string>();
        }

        #endregion

        #region Metodos Privados
        /// <summary>
        /// Añadir error cuando falla la validacion
        /// </summary>
        /// <param name="propertyName">Propiedad</param>
        /// <param name="errorMessage">Error</param>
        /// <param name="errorType">Tipo de error</param> 
        [DebuggerStepThrough]
        private void AddError(string propertyName, string errorMessage, ErrorType errorType)
        {
            IList<ErrorMessage> propertyErrors;
            if (!errors.TryGetValue(propertyName, out propertyErrors))
            {
                propertyErrors = new List<ErrorMessage>();
                errors.Add(propertyName, propertyErrors);
            }
            if (propertyErrors.Count(err => err.ErrorText == errorMessage && err.ErrorType == errorType) == 0)
                propertyErrors.Add(new ErrorMessage(errorMessage, errorType,propertyName));

            OnErrorsChanged(propertyName);
        }

        /// <summary>
        /// Notificar vía evento el error
        /// </summary>
        /// <param name="propertyName">Propiedad</param>
        private void OnErrorsChanged(string propertyName)
        {
            var errorsChanged = this.ErrorsChanged;
            if (errorsChanged != null)
            {
                try
                {
                    errorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
                }
                catch (NullReferenceException)
                {
                }
            }
        }
        #endregion
    }
}